﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Nomo.UnityCoreModule
{
    /// <summary>
    /// 提供一些用于补充 <see cref="System.Linq.Enumerable"/> 的扩展方法。
    /// </summary>
    public static class NomoEnumerable
    {
        /// <summary>
        /// 返回打乱后的序列。
        /// </summary>
        /// <param name="source">序列。</param>
        /// <typeparam name="TSource"><paramref name="source"/> 中的成员的类型。</typeparam>
        /// <returns>包含打乱后的成员的序列。</returns>
        /// <exception cref="ArgumentNullException"><paramref name="source"/> 为 <see langword="null"/> .</exception>
        public static IEnumerable<TSource> Shuffle<TSource>(this IEnumerable<TSource> source)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            var list = source.ToList();
            while (list.Count != 0)
            {
                var index   = UnityEngine.Random.Range(0, list.Count);
                var element = list[index];
                list.RemoveAt(index);
                yield return element;
            }
        }

        /// <summary>
        /// 返回序列中的一个随机成员。
        /// </summary>
        /// <param name="source">序列。</param>
        /// <typeparam name="TSource"><paramref name="source"/> 中的成员的类型。</typeparam>
        /// <returns>序列中的一个随机成员。</returns>
        /// <exception cref="ArgumentNullException"><paramref name="source"/> 为 <see langword="null"/> .</exception>
        /// <exception cref="InvalidOperationException"><paramref name="source"/> 中没有成员。</exception>
        public static TSource Random<TSource>(this IEnumerable<TSource> source)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            switch (source)
            {
                case TSource[] array:
                    return array.Length != 0 ? array[UnityEngine.Random.Range(0, array.Length)] : throw new InvalidOperationException($"{nameof(source)} 中没有成员。");
                case IList<TSource> list:
                    return list.Count != 0 ? list[UnityEngine.Random.Range(0, list.Count)] : throw new InvalidOperationException($"{nameof(source)} 中没有成员。");
                default:
                    var toList = source.ToList();
                    return toList.Count != 0 ? toList[UnityEngine.Random.Range(0, toList.Count)] : throw new InvalidOperationException($"{nameof(source)} 中没有成员。");
            }
        }

        /// <summary>
        /// 返回序列中的一个满足指定条件的随机成员。
        /// </summary>
        /// <param name="source">序列。</param>
        /// <param name="predicate">条件。</param>
        /// <typeparam name="TSource"><paramref name="source"/> 中的成员的类型。</typeparam>
        /// <returns>序列中的一个满足指定条件的随机成员。</returns>
        /// <exception cref="ArgumentNullException"><paramref name="source"/> 或 <paramref name="predicate"/> 为 <see langword="null"/> .</exception>
        /// <exception cref="InvalidOperationException"><paramref name="source"/> 中没有满足 <paramref name="predicate"/> 的成员。</exception>
        public static TSource Random<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (predicate == null)
            {
                throw new ArgumentNullException(nameof(predicate));
            }

            return source.Where(predicate).Random();
        }

        /// <summary>
        /// 返回序列中的一个随机成员。如果序列中没有成员，则返回默认值。
        /// </summary>
        /// <param name="source">序列。</param>
        /// <typeparam name="TSource"><paramref name="source"/> 中的成员的类型。</typeparam>
        /// <returns>序列中的一个随机成员，或（当序列中没有成员时）默认值。</returns>
        /// <exception cref="ArgumentNullException"><paramref name="source"/> 为 <see langword="null"/> .</exception>
        public static TSource RandomOrDefault<TSource>(this IEnumerable<TSource> source)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            switch (source)
            {
                case TSource[] array:
                    return array.Length != 0 ? array[UnityEngine.Random.Range(0, array.Length)] : default;
                case IList<TSource> list:
                    return list.Count != 0 ? list[UnityEngine.Random.Range(0, list.Count)] : default;
                default:
                    var toList = source.ToList();
                    return toList.Count != 0 ? toList[UnityEngine.Random.Range(0, toList.Count)] : default;
            }
        }

        /// <summary>
        /// 返回序列中的一个满足指定条件的随机成员。如果序列中没有满足条件的成员，则返回默认值。
        /// </summary>
        /// <param name="source"></param>
        /// <param name="predicate"></param>
        /// <typeparam name="TSource"></typeparam>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static TSource RandomOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (predicate == null)
            {
                throw new ArgumentNullException(nameof(predicate));
            }

            return source.Where(predicate).RandomOrDefault();
        }

        /// <summary>
        /// 返回序列中随机的指定数量个成员。
        /// </summary>
        /// <param name="source">序列。</param>
        /// <param name="count">数量。</param>
        /// <typeparam name="TSource"><paramref name="source"/> 中的成员的类型。</typeparam>
        /// <returns>序列中随机的指定数量个成员。</returns>
        /// <exception cref="ArgumentNullException"><paramref name="source"/> 为 <see langword="null"/> .</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> count 小于 0 .</exception>
        /// <exception cref="InvalidOperationException"><paramref name="source"/> 中成员的个数小于 <paramref name="count"/> count .</exception>
        public static IEnumerable<TSource> TakeRandom<TSource>(this IEnumerable<TSource> source, int count)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (count < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(count));
            }

            if (count == 0)
            {
                yield break;
            }

            var list        = source.ToList();
            var returnCount = 0;
            while (list.Count != 0 && returnCount != count)
            {
                checked
                {
                    ++returnCount;
                }
                var index   = UnityEngine.Random.Range(0, list.Count);
                var element = list[index];
                list.RemoveAt(index);
                yield return element;
            }
            if (returnCount != count)
            {
                throw new InvalidOperationException($"{nameof(source)} 中的成员数量不足。");
            }
        }

        /// <summary>
        /// 返回序列中随机的指定数量个满足指定条件的成员。
        /// </summary>
        /// <param name="source">序列。</param>
        /// <param name="count">数量。</param>
        /// <param name="predicate">条件。</param>
        /// <typeparam name="TSource"><paramref name="source"/> 中的成员的类型。</typeparam>
        /// <returns>序列中随机的指定数量个满足指定条件的成员。</returns>
        /// <exception cref="ArgumentNullException"><paramref name="source"/> 或 <paramref name="predicate"/> 为 <see langword="null"/> .</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> count 小于 0 .</exception>
        /// <exception cref="InvalidOperationException"><paramref name="source"/> 的成员数量小于 <paramref name="count"/> count .</exception>
        public static IEnumerable<TSource> TakeRandom<TSource>(this IEnumerable<TSource> source, int count, Func<TSource, bool> predicate)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (predicate == null)
            {
                throw new ArgumentNullException(nameof(predicate));
            }

            return source.Where(predicate).TakeRandom(count);
        }

        /// <summary>
        /// 将序列随机分成指定数量个序列。
        /// </summary>
        /// <param name="source">序列。</param>
        /// <param name="count">数量。</param>
        /// <typeparam name="TSource"><paramref name="source"/> 中的成员的类型。</typeparam>
        /// <returns>将序列随机分成指定数量个序列。</returns>
        /// <exception cref="ArgumentNullException"><paramref name="source"/> 为 <see langword="null"/> .</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> 小于 1 .</exception>
        /// <exception cref="InvalidOperationException"><paramref name="source"/> 的成员数量小于 <paramref name="count"/> .</exception>
        public static IEnumerable<IEnumerable<TSource>> SplitRandom<TSource>(this IEnumerable<TSource> source, int count)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (count < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(count));
            }

            var list = source.ToList();
            if (list.Count < count)
            {
                throw new InvalidOperationException($"{nameof(source)} 中的成员数量小于 {nameof(count)} .");
            }

            var result = new List<TSource>[count];
            for (var i = 0; i < count; i++)
            {
                var resultList = new List<TSource>();
                var index      = UnityEngine.Random.Range(0, list.Count);
                var element    = list[index];
                list.RemoveAt(index);
                resultList.Add(element);
                result[i] = resultList;
            }
            while (list.Count != 0)
            {
                var index   = UnityEngine.Random.Range(0, list.Count);
                var element = list[index];
                list.RemoveAt(index);
                result[UnityEngine.Random.Range(0, count)].Add(element);
            }
            return result.Select(e => e.Shuffle());
        }
    }
}
